﻿using System;
using System.Threading;
using System.Threading.Tasks;

namespace Innovative.Atom.Timer.Single
{
	public class AtomSingleTimer
	{
		#region Propery Fields
		private object _syncRoot = new object();
		private TimeSpan _dueTime = TimeSpan.Zero;
		private Task _task = null;
		private ThreadPriority _threadPriority = ThreadPriority.Normal;
		private volatile bool _isActive = false;
		private volatile bool _isComplete = false;
		private CancellationToken _cancellationToken = CancellationToken.None;
		#endregion

		#region Control Fields
		private Thread _timerThread = null;
		private ManualResetEventSlim _wait = new ManualResetEventSlim(false);
		#endregion

		public AtomSingleTimer()
		{
		}

		public AtomSingleTimer(TimeSpan dueTime)
		{
			this.DueTime = dueTime;
		}

		public AtomSingleTimer(TimeSpan dueTime, Task task)
		{
			this.DueTime = dueTime;
			this.EventTask = task;
		}

		public AtomSingleTimer(TimeSpan dueTime, Task task, ThreadPriority threadPriority)
		{
			this.DueTime = dueTime;
			this.EventTask = task;
			this.ThreadPriority = threadPriority;
		}

		public TimeSpan DueTime
		{
			get
			{
				return _dueTime;
			}
			set
			{
				if (!this.IsActive)
				{
					_dueTime = value;
				}
				else
				{
					throw new AtomTimerActiveException();
				}
			}
		}

		public ThreadPriority ThreadPriority
		{
			get
			{
				return _threadPriority;
			}
			set
			{
				if (!this.IsActive)
				{
					_threadPriority = value;
				}
				else
				{
					throw new AtomTimerActiveException();
				}
			}
		}

		public Task EventTask
		{
			get
			{
				return _task;
			}
			set
			{
				if (!this.IsActive)
				{
					if (value != null)
					{
						_task = value;
					}
					else
					{
						throw new NullReferenceException();
					}
				}
				else
				{
					throw new AtomTimerActiveException();
				}
			}
		}

		public bool IsActive
		{
			get
			{
				return _isActive;
			}
			internal set
			{
				lock (this.SyncRoot)
				{
					_isActive = false;
				}
			}
		}

		public bool IsComplete
		{
			get
			{
				return _isComplete;
			}
			internal set
			{
				lock (this.SyncRoot)
				{
					_isComplete = value;
				}
			}
		}

		public object SyncRoot
		{
			get
			{
				return _syncRoot;
			}
		}

		public CancellationToken CancellationToken
		{
			get
			{
				return _cancellationToken;
			}
			set
			{
				_cancellationToken = value;

				if (_cancellationToken != null)
				{
					_cancellationToken.Register(this._Cancellation);
				}
			}
		}

		public Task Start()
		{
			return this.Start(CancellationToken.None);
		}

		public Task Start(CancellationToken cancellationToken)
		{
			// ***
			// *** Call CheckIsReady to throw an exception if this item
			// *** cannot be started
			// ***
			this._CheckIsReady();

			// ***
			// *** Store the cancellation token
			// ***
			if (cancellationToken != null)
			{
				this.CancellationToken = cancellationToken;
			}			

			// ***
			// *** Start the item
			// ***
			this._InternalStart();

			return this.EventTask;
		}

		public WaitHandle WaitHandle
		{
			get
			{
				return _wait.WaitHandle;
			}
		}

		private void _Cancellation()
		{
			// ***
			// *** Force the wait to end
			// ***
			_wait.Set();
		}

		/// <summary>
		/// Throws an exception if this item is not ready to be started
		/// </summary>
		private void _CheckIsReady()
		{
			// ***
			// *** Ensure a valid task is set
			// ***
			if (this.EventTask == null || this.EventTask.IsCompleted || this.EventTask.IsCanceled || this.EventTask.IsFaulted || this.EventTask.Status != TaskStatus.Created)
			{
				throw new InvalidTaskException();
			}
		}

		private void _InternalStart()
		{
			// ***
			// *** Mark this object as active
			// ***
			this.IsActive = true;

			// ***
			// *** Reset the event
			// ***
			_wait.Reset();

			// ***
			// *** Start a thread that will be blocked 
			// *** for the amount of time specified. Do
			// *** not use a task for this since the task
			// *** scheduler will use a thread pool.
			// ***			
			_timerThread = new Thread(new ThreadStart(_ThreadWait));
			_timerThread.Priority = this.ThreadPriority;
			_timerThread.Start();
		}

		private void _ThreadWait()
		{
			try
			{
				// ***
				// *** Wait the specified amount of time
				// ***
				_wait.Wait(this.DueTime);

				// ***
				// *** Check for a cancel
				// ***
				if (!this.CancellationToken.IsCancellationRequested)
				{
					// ***
					// *** Mark this as inactive
					// ***
					this.IsActive = false;

					// ***
					// *** Mark this as complete
					// ***
					this.IsComplete = true;

					// ***
					// *** Start the task
					// ***
					this.EventTask.Start();
				}
			}
			finally
			{
				// ***
				// *** Set the wait event
				// ***
				_wait.Set();

				// ***
				// *** Set the thread to null
				// ***
				_timerThread = null;
			}
		}
	}
}